#include "TypeSystem.h"
string TypeSystem::llvmTypeToStr(Type *value)
{
    Type::TypeID typeID;
    if (value)
        typeID = value->getTypeID();
    else
        return "Value is nullptr";

    switch (typeID)
    {
    case Type::VoidTyID:
        return "void";
    case Type::HalfTyID:
        return "HalfTy";
    case Type::DoubleTyID:
        return "double";
    case Type::IntegerTyID:
        return "integer";
    case Type::FunctionTyID:
        return "Function";
    case Type::StructTyID:
        return "Struct";
    case Type::PointerTyID:
        return "Pointer";
    case Type::ArrayTyID:
        return "Array";
    default:
        return "Unknown";
    }
}

string TypeSystem::llvmTypeToStr(Value *value)
{
    if (value)
        return llvmTypeToStr(value->getType());
    else
        return "Value is nullptr";
}

TypeSystem::TypeSystem(LLVMContext &context) : llvmContext(context)
{

    addCast(intTy, doubleTy, llvm::CastInst::SIToFP);
    addCast(boolTy, doubleTy, llvm::CastInst::SIToFP);
    addCast(charTy, doubleTy, llvm::CastInst::SIToFP);
    addCast(doubleTy, intTy, llvm::CastInst::FPToSI);
    addCast(charTy, intTy, llvm::CastInst::ZExt);
    addCast(boolTy, intTy, llvm::CastInst::ZExt);
}

void TypeSystem::addStructMember(string structName, string memType, string memName)
{
    if (this->structTypes.find(structName) == this->structTypes.end())
    {
        LogError("Unknown struct name");
    }
    this->structMembers[structName].push_back(std::make_pair(memType, memName));
}

void TypeSystem::addStructType(string name, llvm::StructType *type)
{
    this->structTypes[name] = type;
    this->structMembers[name] = std::vector<TypeNamePair>();
}

Value *TypeSystem::getDefaultValue(string typeStr)
{
    Type *type = this->getVarType(typeStr);
    if (type == this->intTy)
    {
        return ConstantInt::get(type, 0, true);
    }
    else if (type == this->doubleTy)
    {
        return ConstantFP::get(type, 0);
    }
    else if (type == this->boolTy)
    {
        return ConstantInt::get(Type::getInt1Ty(llvmContext), 0, true);
    }
    return nullptr;
}

void TypeSystem::addCast(Type *from, Type *to, CastInst::CastOps op)
{
    if (_castTable.find(from) == _castTable.end())
    {
        _castTable[from] = std::map<Type *, CastInst::CastOps>();
    }
    _castTable[from][to] = op;
}

Value *TypeSystem::cast(Value *value, std::string typeStr, BasicBlock *block)
{
    if (typeStr == "bool")
        value = CastToBoolean(value);
    Type *from = value->getType();
    if(typeStr == "char" && from->isIntegerTy())
      return value;
    if (from->isStructTy())
    {

        std::string fromType = from->getStructName().str();
        if (typeStr != fromType)
        {
            std::string err = "Can not cast from  ";
            err += fromType + " to " + typeStr;
            LogError(err.c_str());
            return nullptr;
        }
        return value;
    }
    Type *type = getVarType(typeStr);

    return cast(value, type, block);
}
Value *TypeSystem::cast(Value *value, Type *type, BasicBlock *block)
{
    Type *from = value->getType();
    if (from->isPointerTy())
    {
        if (!type->isPointerTy())
        {
            LogError("Can not cast pointer to non pointer");
            return nullptr;
        }
        return value;
    }
    if (from == type)
    {
        return value;
    }
    if (_castTable.find(from) == _castTable.end())
    {
        string error = "Type ";
        error += llvmTypeToStr(from) + " has no cast to " + llvmTypeToStr(type);
        LogError(error.c_str());
        return nullptr;
    }
    if (_castTable[from].find(type) == _castTable[from].end())
    {
        string error = "Unable to cast from ";
        error += llvmTypeToStr(from) + " to " + llvmTypeToStr(type);
        LogError(error.c_str());
        return nullptr;
    }

    return CastInst::Create(_castTable[from][type], value, type, "cast", block);
}
bool TypeSystem::isStruct(string typeStr) const
{
    return this->structTypes.find(typeStr) != this->structTypes.end();
}

long TypeSystem::getStructMemberIndex(string structName, string memberName)
{
    if (this->structTypes.find(structName) == this->structTypes.end())
    {
        LogError("Unknown struct name");
        return -1;
    }
    auto &members = this->structMembers[structName];
    for (auto it = members.begin(); it != members.end(); it++)
    {
        if (it->second == memberName)
        {
            return std::distance(members.begin(), it);
        }
    }

    LogError("Unknown struct member");

    return -1;
}
string TypeSystem::getStructMemberType(string structName, string memberName)
{
    string err = "";
    if (this->structTypes.find(structName) == this->structTypes.end())
    {
        LogError("Unknown struct name");
        return err;
    }
    auto &members = this->structMembers[structName];
    for (auto it = members.begin(); it != members.end(); it++)
    {
        if (it->second == memberName)
        {
            return it->first;
        }
    }

    LogError("Unknown struct member");

    return err;
}
Type *TypeSystem::getVarType(string valType)
{

    if (valType == "int")
    {
        return this->intTy;
    }
    if (valType == "double")
    {
        return this->doubleTy;
    }
    if (valType == "bool")
    {
        return this->boolTy;
    }
    if (valType == "char")
    {
        return this->charTy;
    }
    if (valType == "void")
    {
        return this->voidTy;
    }
    if (valType == "string")
    {
        return this->stringTy;
    }

    if (this->structTypes.find(valType) != this->structTypes.end())
        return this->structTypes[valType];

    return nullptr;
}
